/*
 * SharePoint RegEx Search
 * 
 * This tool allows all documents in a given Microsoft Office SharePoint 
 * Services site to be searched for occurences of a given 'regular expressions'
 * pattern.
 * 
 * Search results are displayed to standard out in CSV format, including the 
 * matching string and the full document path.
 * 
 * Supported document types include Word, Excel, and PowerPoint.
 * 
 * Copyright 2008, JW Secure, Inc (http://www.jwsecure.com). 
 * Permission to reuse this code is granted under the terms of the Apache 
 * License 2.0 (http://www.apache.org/licenses/).
 */

using System;
using System.Collections.Generic;
using System.Text;
using SharepointSearch.ShareSite;
using Microsoft.Office.Interop;
using Microsoft.Office.Interop.Excel;
using Microsoft.Office.Interop.PowerPoint;
using System.Text.RegularExpressions;
using Microsoft.Office.Core;
using System.Reflection;
using System.Net;
using System.IO;
using Microsoft.SharePoint;

namespace SharepointSearch
{
    class Program
    {
        //max chars display after string being found.
        static int maxToDisplay = 50;

        static void searchRegExpString(
            string text, 
            string regExp, 
            string displayFileName, 
            string prefix)
        {
            if (text == null || regExp == null)
                return;

            //bool found = false;
            Regex reg = new Regex(regExp);
            Match m = reg.Match(text);
            while (m.Success)
            {
                int length = 0;
                while (m.Index + length < text.Length && length < maxToDisplay)
                {
                    if (text[m.Index + length] == ' '  || 
                        text[m.Index + length] == ','  ||
                        text[m.Index + length] == '\r')
                    {
                        break;
                    }
                    length++;
                }
                string subString = text.Substring(m.Index, length);
                while (subString.EndsWith("\r"))
                {
                    subString = text.Substring(m.Index, --length);
                }

                Console.WriteLine(text.Substring(m.Index, length) + "," + displayFileName + "," + prefix);
                m = m.NextMatch();
            }
        }

        static void searchWordDocument(
            string path, 
            string regExp, 
            string displayFileName)
        {
            Microsoft.Office.Interop.Word.ApplicationClass wordApp = null;
            Microsoft.Office.Interop.Word.Document doc = null;
            try
            {
                wordApp = new Microsoft.Office.Interop.Word.ApplicationClass();
                object file = path;
                object nullobj = System.Reflection.Missing.Value;
                bool breadonly = true;
                bool bvisible = true;
                object readOnly = breadonly;
                object ovisible = bvisible;

                doc = wordApp.Documents.Open(ref file,
                    ref nullobj, ref readOnly, ref nullobj, ref nullobj, ref nullobj,
                    ref nullobj, ref nullobj, ref nullobj, ref nullobj, ref nullobj,
                    ref ovisible, ref nullobj, ref nullobj, ref nullobj, ref nullobj);

                Microsoft.Office.Interop.Word.Document doc1 = wordApp.ActiveDocument;
                searchWordDocumentProperties(doc1, regExp, displayFileName, "MetaData");
                searchRegExpString(doc1.Content.Text, regExp, displayFileName, "0");
            }
            catch (Exception)
            {
            }
            finally
            {
                object nullobj = System.Reflection.Missing.Value;
                object save = false;
                if (doc != null)
                    doc.Close(ref save, ref nullobj, ref nullobj);
                if(wordApp != null)
                    wordApp.Quit(ref save, ref nullobj, ref nullobj);
            }
        }

        static void searchPptDocument(
            string path, 
            string regExp, 
            string displayFileName)
        {
            Microsoft.Office.Interop.PowerPoint.Application ppApp = null;
            Microsoft.Office.Interop.PowerPoint.Presentation presentation = null;
            try
            {
                ppApp = new Microsoft.Office.Interop.PowerPoint.Application();
                ppApp.Visible = MsoTriState.msoTrue;
                Presentations presentations = ppApp.Presentations;
                ppApp.WindowState = PpWindowState.ppWindowMinimized;

                presentation = presentations.Open(path,
                    MsoTriState.msoTrue,
                    MsoTriState.msoFalse,
                    MsoTriState.msoTrue);

                searchPowerPointPresentationWorkbookProperties(presentation, regExp, displayFileName, "MetaData");

                int num = 0;
                foreach (Slide slide in presentation.Slides)
                {
                    num++;
                    string prefix = "slide " + num;

                    foreach (Microsoft.Office.Interop.PowerPoint.Shape shape in slide.Shapes)
                    {
                        string[] lines = shape.TextFrame.TextRange.Text.Split('\r');
                        foreach (string line in lines)
                        {
                            searchRegExpString(line, regExp, displayFileName, prefix);
                        }
                    }
                }                
            }
            catch (Exception)
            {
            }
            finally
            {
                if(presentation != null)
                    presentation.Close();
                if(ppApp != null)
                    ppApp.Quit();
            }
        }

        static void searchExcelDocument(
            string path, 
            string regExp, 
            string displayFileName)
        {
            Microsoft.Office.Interop.Excel.Application xlApp = null;
            Workbook xlWorkBook = null;
            try
            {
                xlApp = new Microsoft.Office.Interop.Excel.Application();
                object nullobj = System.Reflection.Missing.Value;
                bool breadonly = true;
                bool bvisible = true;
                object readOnly = breadonly;
                object ovisible = bvisible;

                xlWorkBook = xlApp.Workbooks.Open(path,
                    0,
                    true, 
                    5,
                    nullobj,
                    nullobj, 
                    true, 
                    XlPlatform.xlWindows, 
                    "\t", 
                    false,
                    false,
                    0,
                    true,
                    1,
                    nullobj);

                searchExcelWorkbookProperties(
                    xlWorkBook, 
                    regExp, 
                    displayFileName, 
                    "MetaData");

                foreach (Worksheet xlWorkSheet in xlWorkBook.Worksheets)
                {
                    try
                    {
                        int nInLastRow = xlWorkSheet.Cells.Find("*",
                            System.Reflection.Missing.Value,
                            System.Reflection.Missing.Value,
                            System.Reflection.Missing.Value,
                            XlSearchOrder.xlByRows,
                            XlSearchDirection.xlPrevious,
                            false,
                            System.Reflection.Missing.Value,
                            System.Reflection.Missing.Value).Row;

                        int nInLastCol = xlWorkSheet.Cells.Find("*",
                            System.Reflection.Missing.Value,
                            System.Reflection.Missing.Value,
                            System.Reflection.Missing.Value,
                            XlSearchOrder.xlByColumns,
                            XlSearchDirection.xlPrevious,
                            false,
                            System.Reflection.Missing.Value,
                            System.Reflection.Missing.Value).Column;

                        //row and column are indexed by 1.
                        for (int i = 1; i <= nInLastRow; ++i)
                        {
                            for (int j = 1; j <= nInLastCol; ++j)
                            {
                                if (((Range)xlWorkSheet.Cells[i, j]).Value2 != null)
                                {
                                    string extraInfo = "cell(" + i + " " + j + ")";
                                    searchRegExpString(((Range)xlWorkSheet.Cells[i, j]).Text.ToString(), regExp, displayFileName, extraInfo);
                                }
                            }
                        }
                    }
                    catch (Exception)
                    {
                        //Console.WriteLine("searchExcelDocument: " + ex.Message);
                    }
                }
            }
            catch (Exception)
            {
            }
            finally
            {
                if(xlWorkBook != null)
                    xlWorkBook.Close(null, null, null);
                if(xlApp != null)
                    xlApp.Quit();
            }
        }

        static void searchProperties(
            object properties, 
            string regExp, 
            string displayFileName, 
            string prefix)
        {
            try
            {
                Type propertiesType = properties.GetType();
                object CCount = propertiesType.InvokeMember("Count",
                    System.Reflection.BindingFlags.Default | BindingFlags.GetProperty,
                    null,
                    properties,
                    null);
                int index = 1;

                while (index < (int)CCount)
                {
                    try
                    {
                        object property = propertiesType.InvokeMember("Item",
                            System.Reflection.BindingFlags.Default | BindingFlags.GetProperty,
                            null,
                            properties,
                            new object[] { index });
                        Type propertyType = property.GetType();

                        object propertyName = propertyType.InvokeMember("Name",
                            System.Reflection.BindingFlags.Default | BindingFlags.GetProperty,
                            null,
                            property,
                            new object[] { });
                        searchRegExpString((string)propertyName, regExp, displayFileName, prefix);

                        object propertyValue = propertyType.InvokeMember("Value",
                            System.Reflection.BindingFlags.Default | BindingFlags.GetProperty,
                            null,
                            property,
                            new object[] { });
                        searchRegExpString((string)propertyName, regExp, displayFileName, prefix);
                    }
                    catch (Exception)
                    {
                        //Console.WriteLine("searchProperties: " + ex.Message);
                    }
                    index++;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("searchProperties: " + ex.Message);
            }
        }

        static void searchWordDocumentProperties(
            Microsoft.Office.Interop.Word.Document document,
            string regExp,
            string displayFileName,
            string prefix)
        {
            object customProperties = document.CustomDocumentProperties;
            searchProperties(customProperties, regExp, displayFileName, prefix);

            object builtInProperties = document.BuiltInDocumentProperties;
            searchProperties(builtInProperties, regExp, displayFileName, prefix);
        }

        static void searchExcelWorkbookProperties(Workbook workbook, 
            string regExp,
            string displayFileName,
            string prefix)
        {
            object customProperties = workbook.CustomDocumentProperties;
            searchProperties(customProperties, regExp, displayFileName, prefix);

            object builtInProperties = workbook.BuiltinDocumentProperties;
            searchProperties(builtInProperties, regExp, displayFileName, prefix);
        }

        static void searchPowerPointPresentationWorkbookProperties(Presentation presentation,
            string regExp,
            string displayFileName,
            string prefix)
        {
            object customProperties = presentation.CustomDocumentProperties;
            searchProperties(customProperties, regExp, displayFileName, prefix);

            object builtInProperties = presentation.BuiltInDocumentProperties;
            searchProperties(builtInProperties, regExp, displayFileName, prefix);
        }

        public static void FetchAndSearchFile(string strURL, string searchString)
        {
            if (searchString == null || searchString.Length == 0)
            {
                Console.WriteLine("FetchAndSearchFile: Invalide search string");
                return;
            }
            if (strURL == null || strURL.Length == 0)
            {
                Console.WriteLine("FetchAndSearchFile: Invalid URL");
                return;
            }

            string strFileName = Path.GetFileName(strURL);
            if (strFileName == null || strFileName.Length == 0)
            {
                Console.WriteLine("FetchAndSearchFile: Invalid file name");
                return;
            }

            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(strURL);
                request.Credentials = System.Net.CredentialCache.DefaultCredentials;
                request.Timeout = 10000;
                request.AllowWriteStreamBuffering = false;

                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                Stream responsStream = response.GetResponseStream();

                //Write to disk
                string tempPath = Path.GetTempPath();
                FileStream fs = new FileStream(tempPath + strFileName, FileMode.Create);

                byte[] read = new byte[256];
                int count = responsStream.Read(read, 0, read.Length);

                while (count > 0)
                {
                    fs.Write(read, 0, count);
                    count = responsStream.Read(read, 0, read.Length);
                }

                fs.Close();
                responsStream.Close();
                response.Close();

                string ext = Path.GetExtension(strFileName);
                switch (ext)
                {
                    case ".doc":
                    case ".docx":
                        searchWordDocument(tempPath + strFileName, searchString, strURL);
                        break;
                    case ".xls":
                    case ".xlsx":
                        searchExcelDocument(tempPath + strFileName, searchString, strURL);
                        break;
                    case ".ppt":
                    case ".pptx":
                        searchPptDocument(tempPath + strFileName, searchString, strURL);
                        break;
                    default:
                        Console.WriteLine("FetchAndSearchFile: {0} unrecognize file format.", tempPath + strFileName);
                        break;
                }

                File.Delete(tempPath + strFileName);
            }
            catch (Exception ex)
            {
                Console.WriteLine("FetchAndSearchFile: " + ex.Message);
            }
        }

        static bool isOfficeDocument(string fileName)
        {
            string ext = Path.GetExtension(fileName);
            switch (ext.ToLower())
            {
                case ".doc":
                case ".docx":
                case ".xls":
                case ".xlsx":
                case ".pptx":
                case ".ppt":
                    return true;
                default:
                    return false;
            }
        }

        static void printUsage()
        {
            Console.WriteLine("Usage:");
            Console.WriteLine("SharepointSearch.exe siteUrl regularExpression");
            Console.WriteLine(" Example: to search for the appearance of a Social Security Number:");
            Console.WriteLine(@" SharepointSearch.exe http://localhost/Docs \d{3}-\d{2}-\d{4}");
        }

        static void searchFolder(
            SiteData siteData, 
            string site, 
            string folder, 
            string searchString)
        {
            try
            {
                string fullPath = site;

                if (folder.Length != 0)
                {
                    if (site.EndsWith("/") || folder.StartsWith("/"))
                        fullPath = fullPath + folder;
                    else
                        fullPath = fullPath + "/" + folder;
                }

                _sFPUrl[] enArray;
                siteData.EnumerateFolder(fullPath, out enArray);

                if (enArray == null || enArray.Length == 0)
                {
                    return;
                }

                foreach (_sFPUrl en in enArray)
                {
                    if (en.IsFolder == false)
                    {
                        if (isOfficeDocument(Path.GetFileName(en.Url)))
                        {
                            FetchAndSearchFile(site + "/" + en.Url, searchString);
                        }
                    }
                    else
                    {
                        searchFolder(siteData, site, en.Url, searchString);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("searchFolder: " + ex.Message);
            }
        }

        static void Main(string[] args)
        {
            try
            {
                if (args.Length != 2)
                {
                    printUsage();
                    return;
                }

                string site = args[0];
                string searchString = args[1]; //@"\d{3}-\d{2}-\d{4}";

                Console.WriteLine("Search office documents on site {0} for {1}:", site, searchString);

                using (SiteData objSiteDataService = new SiteData())
                {
                    objSiteDataService.Credentials = System.Net.CredentialCache.DefaultCredentials;

                    objSiteDataService.Url = site + "/_vti_bin/sitedata.asmx";

                    using (SPSite theSite = new SPSite(site))
                    {
                        using (SPWeb web = theSite.OpenWeb())
                        {
                            foreach (SPList list in web.Lists)
                            {
                                if (list.BaseType == SPBaseType.DocumentLibrary)
                                {
                                    SPDocumentLibrary lib = list as SPDocumentLibrary;
                                    searchFolder(objSiteDataService, site, lib.Title, searchString);
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Main: " + ex.Message);
            }
        }
    }
}
